package hydrap2p.library;

import hydrap2p.ServiceLocation;
import hydrap2p.logging.Level;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import org.apache.thrift.TException;

/**
 * Responsible for maintaining a list of active metadaters, and for forwarding metadating requests to each metadater
 * @author Hufman
 *
 */
public class Metadaters {
	
	private MetadaterCollection metadaters;
	private volatile static Metadaters instance=null;
	
	public Metadaters() {
		metadaters=new MetadaterCollection();
		
		ServiceLocation builtin=new ServiceLocation();
		builtin.setAddress("builtin.filesize");
		builtin.setPort(0);
		metadaters.put("*", builtin, new MetadaterFilesize());
	}

	public static Metadaters getInstance()
	{
		if (instance==null){
			synchronized(Metadaters.class)
			{
				if (instance==null)
					instance=new Metadaters();
			}
		}
		return instance;
	}
	
	public static void shutdown()
	{
		instance=null;
	}
	
	public static String getExtension(String filename)
	{
		int period=filename.lastIndexOf('.');
		if (period>=0)
			return filename.substring(period+1).toLowerCase();
		else
			return "";
	}
	
	public boolean subscribe(String extension, ServiceLocation location)
	{
		Metadater metadater;
		try {
			metadater=new MetadaterThrift(location);
		} catch (TException e) {
			LibraryLogging.log(Level.MISHAP, "Library Metadaters", "Failed to connect to Metadater: "+e.getLocalizedMessage());
			return false;
		}
		
		synchronized(metadaters)
		{
			metadaters.put(extension, location, metadater);
		}
		return true;
	}
	
	/**
	 * Method to help in adding dummy Metadaters which don't have to be full Thrift servers
	 * @param name
	 * @param metadater
	 */
	public void subscribe(String extension, ServiceLocation location, Metadater metadater)
	{
		synchronized(metadaters)
		{
			metadaters.put(extension, location, metadater);
		}
	}
	
	public boolean unsubscribe(String extension, ServiceLocation location)
	{
		boolean ret=false;
		synchronized(metadaters)
		{
			if (location!=null)
				metadaters.remove(extension, location);
		}
		return ret;
	}
	
	/**
	 * Scans for metadata on this file, and sets the appropriate fields in ItemInfo
	 * If force==true, rescan metadata from providers that have returned metadata in the past
	 * @param item
	 * @param force
	 */
	public boolean scanMetadata(ItemInfo item, boolean force)
	{
		boolean changed=false;
		Map<ServiceLocation,Metadater> mymetadaters;
		List<ServiceLocation> failedmetadaters = new LinkedList<ServiceLocation>();
		Map<String, String> newmetadata = new TreeMap<String, String>();	// the new list of metadata
		
		String extension=getExtension(item.name);
		mymetadaters = metadaters.getList(extension);
		
		if (!force)
		{
			newmetadata.putAll(item.metadata);		// load previous metadata, if we're not forcing a complete scan
		}
		
		for (Entry<ServiceLocation, Metadater> entry : mymetadaters.entrySet())
		{
			ServiceLocation location=entry.getKey();
			try {
				// Load this metadater's metadata
				newmetadata.putAll(mymetadaters.get(location).parseMetadata(item.toPathFile()));
				changed=true;
			} catch (TException e) {
				// Remove this metadater, because it failed
				LibraryLogging.log(Level.INFO, "Library Metadaters", "Failed to parse with Metadater during scan of "+item.getPathname()+": "+e.getLocalizedMessage());
				failedmetadaters.add(location);
			} catch (Exception e) {
				// Remove this metadater, because it failed
				LibraryLogging.log(Level.INFO, "Library Metadaters", "Failed to parse with Metadater during scan of "+item.getPathname()+": "+e.getLocalizedMessage());
				e.printStackTrace();
			}
		}
		
		item.metadata=newmetadata;
		metadaters.removeAll(extension, failedmetadaters);
		
		return changed;
	}
}
